#include "pkgframe.h"
#include "aes.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "sha1.h"
#include "com_def.h"
#include "fun_file.h"
#include "fun_str.h"

#define FRAME_BASE_SIZE 654 // 每个单元块的大小
#define SECTOR_SIZE 16
#define __bswap_32(x)    \
     ((((x) & 0xff000000) >> 24)  \
     | (((x) & 0x00ff0000) >>  8) \
     | (((x) & 0x0000ff00) <<  8) \
     | (((x) & 0x000000ff) << 24))

static const char *ver = "v0.0.17";
static char version_name[64];

#ifdef MULTIPLY_THREADS_SUPPORT
#include <pthread.h>
#include <unistd.h>
#include "cpu_info.h"
#define MAX_SUPPORT_THREADS 16
#define MAX_THREAD_HANDLE_SIZE (1<<20)  // 1M
#define MINIMUM_SIZE_USING_THREAD (1<<24)   // 16M
// #define MAX_THREAD_HANDLE_SIZE 128
// #define MINIMUM_SIZE_USING_THREAD 1024
struct thread_arg {
    char *data;
    int data_len;
    int offset;     // -1 stop, else writing data offset
    pthread_mutex_t *mutex;
    AESInfo_t *ainfo;
};
static int cpu_core = 0;

static int get_cpu_core() {
    struct CpuUsageRate cur;
    CpuUsageRateInit(&cur);
    return cur.cpu_num;
}

void *threadDecrypt(void *arg) {
    if (!arg) pthread_exit(NULL);
    struct thread_arg *thr_arg = (struct thread_arg *)arg;

    while (thr_arg->offset != -1) {
        if (thr_arg->data_len > 0) {
            pthread_mutex_lock(thr_arg->mutex);
            // LOG_DEBUG("offset: 0x%x, data_len: %d", thr_arg->offset, thr_arg->data_len);
            for (int i=0;i+SECTOR_SIZE<=thr_arg->data_len;i+=SECTOR_SIZE) {
                AESDecrypt(thr_arg->ainfo, (unsigned char*)thr_arg->data+thr_arg->offset+i, (unsigned char*)thr_arg->data+thr_arg->offset+i, SECTOR_SIZE);
            }
            thr_arg->data_len = 0;  // 解密data_len作标定物
            pthread_mutex_unlock(thr_arg->mutex);
        } else if (thr_arg->data_len < 0) {
            LOG_ERROR("data len is < 0");
        } else {
            usleep(1);
        }
    }
    pthread_exit(NULL);
}

void *threadEncrypt(void *arg) {
    if (!arg) pthread_exit(NULL);
    struct thread_arg *thr_arg = (struct thread_arg *)arg;

    while (1) {
        if (thr_arg->offset == -1) {
            break;
        } else if (thr_arg->offset == 0) {
            usleep(1);
        } else {
            pthread_mutex_lock(thr_arg->mutex);
            for (int i=0;i+SECTOR_SIZE<=thr_arg->data_len;i+=SECTOR_SIZE) {
                AESEncrypt(thr_arg->ainfo, (unsigned char*)thr_arg->data+i, (unsigned char*)thr_arg->data+i, SECTOR_SIZE);
            }
            // AESEncrypt(thr_arg->ainfo, thr_arg->data, thr_arg->data, (thr_arg->data_len/SECTOR_SIZE)*SECTOR_SIZE);
            thr_arg->offset = 0;
            pthread_mutex_unlock(thr_arg->mutex);
        }
    }

    pthread_exit(NULL);
}
#endif

const char *package_frame_version(void) {
    return ver;
}

const char *package_frame_version_full(void) {
    #ifdef MULTIPLY_THREADS_SUPPORT
    sprintf(version_name,"%s %d threads support", ver, MAX_SUPPORT_THREADS);
    #else
    strcpy(version_name, ver);
    #endif
    return version_name;
}

int package_frame_init(struct package_frame *pak, char *ukey, char isEncrypt)
{
    char b[32];
    if (sizeof(pak->unit->cip_sign) < SHA1_DIGEST_LEN || sizeof(pak->unit->pla_sign) < SHA1_DIGEST_LEN) 
        LOG_ASSERT("cip_sign and pla_sign must langer than SHA1_DIGEST_LEN");
    if (sizeof(int) != 4) LOG_ASSERT("sizeof(int) is not 4 bytes, please change file size and block_size into 4 bytes!");
    if (!ukey) {
        for (int i=0;i<32;i++) b[i] = (i<<1)+3;
    } else {
        for (int i=0;i<32;i++) b[i] = ukey[i];
    }
    pak->ainfo.mode = AES_MODE_CBC;
    pak->ainfo.type = AES128;
    pak->ainfo.padding = AES_NoPadding;
    AESInit(&pak->ainfo, (unsigned char*)b, (unsigned char*)b);
    pak->unit = NULL;
    pak->head = NULL;
    pak->isEncrypt = isEncrypt;

    #ifdef MULTIPLY_THREADS_SUPPORT
    cpu_core = get_cpu_core();
    LOG_DEBUG("cpu_core_1: %d", cpu_core);
    if (cpu_core > MAX_SUPPORT_THREADS-1) cpu_core = MAX_SUPPORT_THREADS-1;
    LOG_DEBUG("cpu_core_2: %d", cpu_core);
    #endif

    return 0;
}

static int isLittleEndian() {
    union un
    {
        char c;
        int i;
    } u = {1};
    return u.c;
}

int package_frame_append_folder(struct package_frame *pak, 
                         const char *filename,
                         const char *fileFolder,
                         const char *note,
                         const char *version,
                         char type,
                         char is_encrypt)
{
    if (fileFolder && strstr(fileFolder, "../")) {
        LOG_ERROR("file folder is not support previous directory");
        return -4;
    }
    
    if (is_directory(filename)) {
        LOG_DEBUG("in directory %s", filename);
        struct file_list_s *list = get_list_files(filename, NULL), *list_f = list;
        #if 1
        while(list_f) {
            LOG_DEBUG("%s --> %s", filename, list_f->fileName);
            list_f = list_f->next;
        }
        list_f = list;
        #endif
        char append_filename[256];
        while (list_f) {
            file_path_name_paste(append_filename, filename, list_f->fileName);
            // if (filename[strlen(filename)-1] == '/')
            //     snprintf(append_filename, sizeof(append_filename), "%s%s", filename, list_f->fileName);
            // else 
            //     snprintf(append_filename, sizeof(append_filename), "%s/%s", filename, list_f->fileName);
            
            LOG_DEBUG("try append - %s", append_filename);
            int ret = package_frame_append_folder(pak, append_filename, fileFolder, note, version, type, is_encrypt);
            if (ret != 0) {
                LOG_ERROR("append '%s' fail: %d", append_filename, ret);
                return -5;
            }
            list_f = list_f->next;
        }
        get_list_files_free(list);
        return 0;
    }
    
    int fs = get_file_size(filename);
    if (fs<0) return -1;
    if (fs < SECTOR_SIZE) LOG_WARN("file '%s' size is too small, please change SECTOR_SIZE to get safer package", filename);
    if (fs < SECTOR_SIZE*10) LOG_WARN("file '%s' size is not larger enough", filename);
    struct p_module_unit *lat = pak->unit;
    if (!pak->unit) {
        pak->unit = (struct p_module_unit*)malloc(sizeof(struct p_module_unit));
        if (!pak->unit) return -2;
        lat = pak->unit;
    } else {
        lat = pak->unit;
        while (lat->next) lat = lat->next;
        lat->next = (struct p_module_unit*)malloc(sizeof(struct p_module_unit));
        if (!lat->next) return -3;
        lat = lat->next;
    }

    memset(lat, 0, sizeof(struct p_module_unit));
    char *tmp = strrstr(filename, "../"), tmp_filefolder[256];
    if (tmp) tmp+=3;
    else tmp = filename;
    char tmp2[256];
    get_file_path_relative(tmp2, tmp);
    if (!fileFolder || fileFolder[0]==0) {
        snprintf(tmp_filefolder, sizeof(tmp_filefolder), "%s", tmp2);
    } else {
        snprintf(tmp_filefolder, sizeof(tmp_filefolder), "%s/%s", fileFolder, tmp2);
    }
    strncpy(lat->fileFolder, tmp_filefolder, sizeof(lat->fileFolder)-1);
    if (note) strncpy(lat->note, note, sizeof(lat->note)-1);
    if (version) strncpy(lat->version, version, sizeof(lat->version)-1);
    strncpy(lat->filename, filename, sizeof(lat->filename)-1);
    lat->type = type;
    lat->data_len = fs;
    lat->isEncrypt = is_encrypt;
    lat->data = NULL;
    LOG_DEBUG("append file %s success", filename);
    return 0;
}

int package_frame_append(struct package_frame *pak, 
                         const char *filename,
                         const char *fileFolder,
                         const char *note,
                         const char *version,
                         char type,
                         char is_encrypt)
{
    if (fileFolder && strstr(fileFolder, "../")) {
        LOG_ERROR("file folder is not support previous directory");
        return -4;
    }
    
    if (is_directory(filename)) {
        int ret = package_frame_append_folder(pak, filename, fileFolder, note, version, type, is_encrypt);
        if (ret != 0) {
            LOG_ERROR("append '%s' fail: %d", filename, ret);
            return -5;
        }
        return 0;
        #if 0
        LOG_DEBUG("in directory %s", filename);
        struct file_list_s *list = get_list_files(filename, NULL), *list_f = list;
        #if 1
        while(list_f) {
            LOG_DEBUG("%s --> %s", filename, list_f->fileName);
            list_f = list_f->next;
        }
        list_f = list;
        #endif
        char append_filename[256];
        while (list_f) {
            file_path_name_paste(append_filename, filename, list_f->fileName);
            // if (filename[strlen(filename)-1] == '/')
            //     snprintf(append_filename, sizeof(append_filename), "%s%s", filename, list_f->fileName);
            // else 
            //     snprintf(append_filename, sizeof(append_filename), "%s/%s", filename, list_f->fileName);
            
            LOG_DEBUG("try append - %s", append_filename);
            int ret = package_frame_append_folder(pak, append_filename, fileFolder, note, version, type, is_encrypt);
            if (ret != 0) {
                LOG_ERROR("append '%s' fail: %d", append_filename, ret);
                return -5;
            }
            list_f = list_f->next;
        }
        get_list_files_free(list);
        return 0;
        #endif
    }
    
    int fs = get_file_size(filename);
    if (fs<0) return -1;
    if (fs < SECTOR_SIZE) LOG_WARN("file '%s' size is too small, please change SECTOR_SIZE to get safer package", filename);
    if (fs < SECTOR_SIZE*10) LOG_WARN("file '%s' size is not larger enough", filename);
    struct p_module_unit *lat = pak->unit;
    if (!pak->unit) {
        pak->unit = (struct p_module_unit*)malloc(sizeof(struct p_module_unit));
        if (!pak->unit) return -2;
        lat = pak->unit;
    } else {
        lat = pak->unit;
        while (lat->next) lat = lat->next;
        lat->next = (struct p_module_unit*)malloc(sizeof(struct p_module_unit));
        if (!lat->next) return -3;
        lat = lat->next;
    }

    memset(lat, 0, sizeof(struct p_module_unit));
    // char *tmp = strrstr(filename, "../"), tmp_filefolder[256];
    // if (tmp) tmp+=3;
    // else tmp = filename;
    // char tmp2[256];
    // get_file_path_relative(tmp2, tmp);
    // if (!fileFolder || fileFolder[0]==0) {
    //     snprintf(tmp_filefolder, sizeof(tmp_filefolder), "%s", tmp2);
    // } else {
    //     snprintf(tmp_filefolder, sizeof(tmp_filefolder), "%s/%s", fileFolder, tmp2);
    // }
    if (fileFolder) strncpy(lat->fileFolder, fileFolder, sizeof(lat->fileFolder)-1);
    if (note) strncpy(lat->note, note, sizeof(lat->note)-1);
    if (version) strncpy(lat->version, version, sizeof(lat->version)-1);
    strncpy(lat->filename, filename, sizeof(lat->filename)-1);
    lat->type = type;
    lat->data_len = fs;
    lat->isEncrypt = is_encrypt;
    lat->data = NULL;
    LOG_DEBUG("append file %s success", filename);
    return 0;
}

static char *findCharLast(char *src, char ch) 
{
    char *dest = NULL;
    while (*src)
    {
        if (*src == ch) dest = src; 
        src++;
    }
    return dest;
}

int package_frame_generate(struct package_frame *pak, char *filename) 
{
    if (!pak || !filename) return -1;
    FILE *fp = fopen(filename, "wb");
    if(fp == NULL) {
        perror("fopen");
        return -2;
    }
    int cnt=0;
    char tmp[16];
    struct p_module_unit *lat = pak->unit;
    
    while (lat)
    {
        FILE *fpt = fopen(lat->filename, "rb");
        if(fpt == NULL) {
            perror("fopen");
            LOG_ERROR("open %s fail", lat->filename);
            lat = lat->next;
            continue;
        }
        
        char tfilename[sizeof(lat->filename)];
        char *s = findCharLast(lat->filename, '/');
        if (s) s++;
        else s = lat->filename;
        char *s2 = findCharLast(s, '\\');
        if (s2) s2++;
        else s2 = s;
        strcpy(tfilename, s2);
        LOG_DEBUG("tfilename : %s", tfilename);

        fwrite(tfilename, 1, 1, fp);
        fwrite(&lat->isEncrypt, 1, 1, fp);
        // LOG_DEBUG("isEncrypt : %d", lat->isEncrypt);
        fwrite(tfilename, 1, 2, fp);    // backup
        int block_size = SECTOR_SIZE;
        if (isLittleEndian()) {
            block_size = __bswap_32(block_size);
        }
        fwrite(&block_size, 1, sizeof(block_size), fp);
        fwrite(tfilename, 1, 8, fp);  // backup
        strncpy(tmp, ver, sizeof(tmp));
        fwrite(tmp, 1, 16, fp);
        fwrite(lat->fileFolder, 1, sizeof(lat->fileFolder), fp);
        fwrite(tfilename, 1, sizeof(tfilename), fp);
        fwrite(lat->version, 1, sizeof(lat->version), fp);
        fwrite(lat->note, 1, sizeof(lat->note), fp);
        int data_len = lat->data_len;
        if (isLittleEndian()) {
            data_len = __bswap_32(data_len);
        }
        fwrite(&data_len, 1, sizeof(data_len), fp);
        fwrite(&lat->type, 1, sizeof(lat->type), fp);
        LOG_DEBUG("data_len : %d", lat->data_len);

        sha1_ctx_t cip_ctx, pla_ctx;
        crypto_sha1_init(&cip_ctx);
        crypto_sha1_init(&pla_ctx);
        if (lat->isEncrypt) {
            LOG_INFO("start encrypt ===");
            #ifdef MULTIPLY_THREADS_SUPPORT
            if (lat->data_len>MINIMUM_SIZE_USING_THREAD && cpu_core>2) {
                // int offset=0, ret;
                pthread_t pt_list[MAX_SUPPORT_THREADS-1];
                pthread_mutex_t mpt_list[MAX_SUPPORT_THREADS-1];
                struct thread_arg thr_arg[MAX_SUPPORT_THREADS-1];

                for (int i=0;i<cpu_core;i++) {
                    thr_arg[i].data = (char*)malloc(MAX_THREAD_HANDLE_SIZE);
                    if (!thr_arg[i].data) {
                        LOG_ERROR("not enough RAM for malloc");
                        while(i--) free(thr_arg[i].data);
                        fclose(fpt);
                        return cnt;
                    }
                    pthread_mutex_init(&mpt_list[i], NULL);
                    thr_arg[i].offset = 1;
                    thr_arg[i].mutex = &mpt_list[i];
                    thr_arg[i].ainfo = &pak->ainfo;
                    thr_arg[i].data_len = fread(thr_arg[i].data, 1, MAX_THREAD_HANDLE_SIZE, fpt);
                    if (thr_arg[i].data_len != MAX_THREAD_HANDLE_SIZE) {
                        LOG_ERROR("I/O read performance is weak that just get %d bytes each", thr_arg[i].data_len);
                        while(i) free(thr_arg[i--].data);
                        fclose(fpt);
                        return cnt;
                    }

                    crypto_sha1_update(&pla_ctx, (uint8_t*)(thr_arg[i].data), thr_arg[i].data_len);
                    pthread_create(&pt_list[i], NULL, threadEncrypt, &thr_arg[i]);
                }
                int i=0,last_alarm=0;
                while (1) {
                    // LOG_DEBUG("i = %d", i);
                    pthread_mutex_lock(thr_arg[i].mutex);
                    crypto_sha1_update(&cip_ctx, (uint8_t*)(thr_arg[i].data), thr_arg[i].data_len);
                    fwrite(thr_arg[i].data, 1, thr_arg[i].data_len, fp);
                    thr_arg[i].data_len = fread(thr_arg[i].data, 1, MAX_THREAD_HANDLE_SIZE, fpt);
                    crypto_sha1_update(&pla_ctx, (uint8_t*)(thr_arg[i].data), thr_arg[i].data_len);
                    if (!thr_arg[i].data_len) {
                        pthread_mutex_unlock(thr_arg[i].mutex);
                        int j = i+1;
                        if (j >= cpu_core) j = 0;
                        while (j!=i) {
                            if (thr_arg[j].offset == 0) {
                                pthread_mutex_lock(thr_arg[j].mutex);
                                crypto_sha1_update(&cip_ctx, (uint8_t*)(thr_arg[j].data), thr_arg[j].data_len);
                                fwrite(thr_arg[j].data, 1, thr_arg[j].data_len, fp);
                                pthread_mutex_unlock(thr_arg[j].mutex);
                                if (++j >= cpu_core) j = 0;
                            } else {
                                usleep(1);
                            }
                        }
                        break;
                    } else if (last_alarm) {
                        LOG_ERROR("I/O read performance have problem");
                        last_alarm = 2;
                        pthread_mutex_unlock(thr_arg[i].mutex);
                        break;
                    } else if (thr_arg[i].data_len != MAX_THREAD_HANDLE_SIZE) last_alarm = 1;
                    
                    thr_arg[i].offset = 1;
                    pthread_mutex_unlock(thr_arg[i].mutex);

                    if (++i == cpu_core) i=0;
                }
                for (i=0;i<cpu_core;i++) {
                    pthread_mutex_lock(thr_arg[i].mutex);
                    thr_arg[i].offset = -1;
                    pthread_mutex_unlock(thr_arg[i].mutex);
                }
                for (i=0;i<cpu_core;i++) {
                    pthread_join(pt_list[i], NULL);
                    free(thr_arg[i].data);
                    pthread_mutex_destroy(thr_arg[i].mutex);
                }
                if (last_alarm == 2) return cnt;
            } else
            #endif  /* MULTIPLY_THREADS_SUPPORT */
            {
                while (1) {
                    char buff[SECTOR_SIZE];
                    int rlen = fread(buff, 1, sizeof(buff), fpt);
                    if (rlen <= 0) {
                        break;
                    }
                    crypto_sha1_update(&pla_ctx, (uint8_t*)buff, rlen);
                    if (rlen == sizeof(buff)) AESEncrypt(&pak->ainfo, (unsigned char*)buff, (unsigned char*)buff, sizeof(buff));
                    crypto_sha1_update(&cip_ctx, (uint8_t*)buff, rlen);

                    fwrite(buff, 1, rlen, fp);
                }
            }
            LOG_INFO("end encrypt ===");
        } else {    // encrypt
            LOG_INFO("start transport ===");
            while (1) {
                char buff[100<<10];
                int rlen = fread(buff, 1, sizeof(buff), fpt);
                if (rlen <= 0) {
                    break;
                }
                crypto_sha1_update(&pla_ctx, (uint8_t*)buff, rlen);
                fwrite(buff, 1, rlen, fp);
            }
            LOG_INFO("end transport ===");
        }
        fclose(fpt);

        crypto_sha1_update(&pla_ctx, (uint8_t*)lat->fileFolder, strlen(lat->fileFolder));
        crypto_sha1_update(&pla_ctx, (uint8_t*)tfilename, strlen(tfilename));
        crypto_sha1_update(&pla_ctx, (uint8_t*)lat->version, strlen(lat->version));
        crypto_sha1_update(&pla_ctx, (uint8_t*)lat->note, strlen(lat->note));
        crypto_sha1_update(&pla_ctx, (uint8_t*)(&data_len), sizeof(data_len));
        crypto_sha1_update(&pla_ctx, (uint8_t*)(&lat->type), sizeof(lat->type));
        
        crypto_sha1_update(&cip_ctx, (uint8_t*)lat->fileFolder, strlen(lat->fileFolder));
        crypto_sha1_update(&cip_ctx, (uint8_t*)tfilename, strlen(tfilename));
        crypto_sha1_update(&cip_ctx, (uint8_t*)lat->version, strlen(lat->version));
        crypto_sha1_update(&cip_ctx, (uint8_t*)lat->note, strlen(lat->note));
        crypto_sha1_update(&cip_ctx, (uint8_t*)(&data_len), sizeof(data_len));
        crypto_sha1_update(&cip_ctx, (uint8_t*)(&lat->type), sizeof(lat->type));
        
        crypto_sha1_final(&pla_ctx, (uint8_t*)lat->pla_sign);
        crypto_sha1_final(&cip_ctx, (uint8_t*)lat->cip_sign);

        fwrite(lat->pla_sign, 1, SHA1_DIGEST_LEN, fp);
        fwrite(lat->cip_sign, 1, SHA1_DIGEST_LEN, fp);
        fwrite(tfilename, 1, 1, fp);
        cnt++;
        LOG_INFO("insert %s success", lat->filename);
        lat = lat->next;
    }
    fclose(fp);
    return cnt;
}

static void p_module_unit_free(struct p_module_unit *u) {
    if (u) {
        p_module_unit_free(u->next);
        free(u->data);
        free(u);
    }
}

void package_frame_free(struct package_frame *pak) {
    p_module_unit_free(pak->unit);
    pak->unit = NULL;
}

static char *_read_file_all(char *fn, int fs) {
    char *all;
    int ret,cnt=0;
    if (fs<=0) return NULL;
    all = (char*)malloc(fs);
    if (!all) return NULL;
    FILE *fp = fopen(fn, "rb");
    if (!fp) {
        perror("fopen");
        free(all);
        return NULL;
    }
    
    while (1) {
        ret = fread(all+cnt, 1, fs-cnt, fp);
        // LOG_DEBUG("fread %d", ret);
        if (ret<=0) break;
        cnt+=ret;
    }
    fclose(fp);
    if (fs != cnt) {
        LOG_WARN("read %d bytes, but file have %d bytes\n", cnt, fs);
        free(all);
        return NULL;
    }
    return all;
}

static int _read_one_frame(struct package_frame *pak, struct p_module_unit *u, char *data) {
    int os = 0, dalen_tm, head;
    memset(u, 0, sizeof(struct p_module_unit));

    head = *(data+os);
    os += 1;
    u->isEncrypt = *(data+os);
    LOG_DEBUG("isEncrypt = %d", u->isEncrypt);
    os += 1;
    // backup
    os += 2;
    int block_size = isLittleEndian()?__bswap_32(*(int*)(data+os)):*(int*)(data+os);
    os += sizeof(block_size);
    // LOG_DEBUG("block_size %d", block_size);
    // backup
    os += 8;    
    char pkgframe_version[20] = {0};
    strncpy(pkgframe_version, data+os, 16);
    // LOG_DEBUG("pkgframe_version %s", pkgframe_version);
    os += 16;
    strncpy(u->fileFolder, data+os, sizeof(u->fileFolder));
    os += sizeof(u->fileFolder);
    strncpy(u->filename, data+os, sizeof(u->filename));
    LOG_DEBUG("u->filename = %.*s", sizeof(u->filename), u->filename);
    os += sizeof(u->filename);
    strncpy(u->version, data+os, sizeof(u->version));
    os += sizeof(u->version);
    strncpy(u->note, data+os, sizeof(u->note));
    os += sizeof(u->note);
    dalen_tm = *(int*)(data+os);
    if (isLittleEndian()) {
        u->data_len = __bswap_32(dalen_tm);
    } else {
        u->data_len = dalen_tm;
    }
    LOG_DEBUG("data_len = %d", u->data_len);
    os += sizeof(u->data_len);
    if (u->data_len < 0) {
        return -(os+SHA1_DIGEST_LEN+SHA1_DIGEST_LEN+1);
    }
    u->type = *(char*)(data+os);
    os += sizeof(u->type);
    u->data = (char*)malloc(u->data_len);
    if (!u->data) {
        LOG_ERROR("malloc fail");
        return -(os+u->data_len+(SHA1_DIGEST_LEN<<1)+1);
    }
    memcpy(u->data, data+os, u->data_len);
    os += u->data_len;
    memcpy(u->pla_sign, data+os, SHA1_DIGEST_LEN);
    os += SHA1_DIGEST_LEN;
    memcpy(u->cip_sign, data+os, SHA1_DIGEST_LEN);
    os += SHA1_DIGEST_LEN;
    if (*(data+os) != head || (u->isEncrypt && !pak->isEncrypt)) {
        free(u->data);
        return -(os+1);
    }

    // check
    char sha1_arr[SHA1_DIGEST_LEN];
    sha1_ctx_t ctx;
    if (u->isEncrypt) {
        crypto_sha1_init(&ctx);
        crypto_sha1_update(&ctx, (uint8_t*)u->data, u->data_len);
        crypto_sha1_update(&ctx, (uint8_t*)u->fileFolder, strlen(u->fileFolder));
        crypto_sha1_update(&ctx, (uint8_t*)u->filename, strlen(u->filename));
        crypto_sha1_update(&ctx, (uint8_t*)u->version, strlen(u->version));
        crypto_sha1_update(&ctx, (uint8_t*)u->note, strlen(u->note));
        crypto_sha1_update(&ctx, (uint8_t*)(&dalen_tm), sizeof(dalen_tm));
        crypto_sha1_update(&ctx, (uint8_t*)(&u->type), sizeof(u->type));
        crypto_sha1_final(&ctx, (uint8_t*)sha1_arr);
        if (memcmp(sha1_arr, u->cip_sign, SHA1_DIGEST_LEN)) {
            free(u->data);
            LOG_ERROR("%.*s cip_sign fail", (int)sizeof(u->filename), u->filename);
            return -(os+1);
        }

        LOG_INFO("start decrypt ===");
        #ifdef MULTIPLY_THREADS_SUPPORT
        if (u->data_len>MINIMUM_SIZE_USING_THREAD && cpu_core>2) {
            int r_offset=0, ret;
            pthread_t pt_list[MAX_SUPPORT_THREADS-1];
            pthread_mutex_t mpt_list[MAX_SUPPORT_THREADS-1];
            struct thread_arg thr_arg[MAX_SUPPORT_THREADS-1];
            // init
            for (int i=0;i<cpu_core;i++) {
                pthread_mutex_init(&mpt_list[i], NULL);
                thr_arg[i].data = u->data;
                thr_arg[i].offset = 0;
                thr_arg[i].data_len = 0;
                thr_arg[i].mutex = &mpt_list[i];
                thr_arg[i].ainfo = &pak->ainfo;
                pthread_create(&pt_list[i], NULL, threadDecrypt, &thr_arg[i]);
            }
            // decrypt
            while (r_offset < u->data_len) {
                int i=0;
                for (;i<cpu_core;i++) {
                    if (thr_arg[i].data_len == 0) {
                        ret = pthread_mutex_trylock(thr_arg[i].mutex);
                        if (ret == 0) {
                            thr_arg[i].offset = r_offset;
                            thr_arg[i].data_len = ((int)(r_offset+MAX_THREAD_HANDLE_SIZE))>u->data_len?u->data_len-r_offset:MAX_THREAD_HANDLE_SIZE;
                            r_offset+=thr_arg[i].data_len;
                            // LOG_DEBUG("send %d bytes to %d thread", thr_arg[i].data_len, i);
                            pthread_mutex_unlock(thr_arg[i].mutex);
                            i++;
                            break;
                        }
                    }
                }
                if (i==cpu_core) usleep(1);
            }
               
            // clean up
            for (int i=0;i<cpu_core;i++) {
                while (thr_arg[i].data_len != 0) usleep(1);
                pthread_mutex_lock(thr_arg[i].mutex);
                thr_arg[i].offset = -1;
                pthread_mutex_unlock(thr_arg[i].mutex);
            }
            for (int i=0;i<cpu_core;i++) {
                pthread_join(pt_list[i], NULL);
                pthread_mutex_destroy(thr_arg[i].mutex);
            }
            if (r_offset != u->data_len) {
                LOG_ERROR("r_offset(%d) is not fix data_len(%d)", r_offset, u->data_len);
                return -(os+1);
            }
        } else
        #endif 
        {
            for (int i=0;i+SECTOR_SIZE<=u->data_len;i+=SECTOR_SIZE) {
                AESDecrypt(&pak->ainfo, (unsigned char*)u->data+i, (unsigned char*)u->data+i, SECTOR_SIZE);
                // LOG_DEBUG("decrypt %d", i);
            }
        }
        LOG_INFO("end decrypt ===");
    } 
    
    crypto_sha1_init(&ctx);
    crypto_sha1_update(&ctx, (uint8_t*)u->data, u->data_len);
    crypto_sha1_update(&ctx, (uint8_t*)u->fileFolder, strlen(u->fileFolder));
    crypto_sha1_update(&ctx, (uint8_t*)u->filename, strlen(u->filename));
    crypto_sha1_update(&ctx, (uint8_t*)u->version, strlen(u->version));
    crypto_sha1_update(&ctx, (uint8_t*)u->note, strlen(u->note));
    crypto_sha1_update(&ctx, (uint8_t*)(&dalen_tm), sizeof(dalen_tm));
    crypto_sha1_update(&ctx, (uint8_t*)(&u->type), sizeof(u->type));
    crypto_sha1_final(&ctx, (uint8_t*)sha1_arr);
    if (memcmp(sha1_arr, u->pla_sign, SHA1_DIGEST_LEN)) {
        LOG_ERROR("%.*s pla_sign fail", (int)sizeof(u->filename), u->filename);
        #if 0
        FILE *fp = fopen("errorFile.txt", "wb");
        if (!fp) {
            perror("fopen");
            return -(os+1);
        }
        LOG_DEBUG("u->data = %x", u->data);
        int ret = fwrite(u->data, 1, u->data_len, fp);
        if (u->data_len != ret) {
            perror("fwrite");
        }
        LOG_DEBUG("ret = %d, data_len = %d", ret, u->data_len);
        fclose(fp);
        #endif

        free(u->data);
        return -(os+1);
    }

    return os+1;
}

static int _get_date_len(char *data) {
    int o = 1 + 1 + 2 + 16 + 4 + 8
            + sizeof(((struct p_module_unit *)0)->fileFolder)
            + sizeof(((struct p_module_unit *)0)->note)
            + sizeof(((struct p_module_unit *)0)->filename)
            + sizeof(((struct p_module_unit *)0)->version);
    int l = *(int*)(data+o);
    if (isLittleEndian()) {
        l = __bswap_32(l);
    }
    return l;
}

int package_frame_read_package(struct package_frame *pak, char *filename) {
    if (!pak || pak->unit) return -1;
    int fs = get_file_size(filename);
    LOG_DEBUG("pak_len = %d", fs);
    char *all = _read_file_all(filename, fs);
    if (!all) return -2;
    struct p_module_unit *ud, *u;
    int ret, offset = 0, cnt=0;
    
    if (fs < FRAME_BASE_SIZE) {
        free(all);
        return -3;
    }
    u = malloc(sizeof(struct p_module_unit));
    if (!u) {
        free(all);
        return -4;
    }
    pak->unit = u;

    while ((fs-offset)>=FRAME_BASE_SIZE) {
        ret = _get_date_len(all+offset);
        LOG_DEBUG("analysis data_len = %d", ret);
        if (ret<0 || (fs-offset-ret)<FRAME_BASE_SIZE) {
            LOG_WARN("residual data is %d but need %d", fs-offset, ret+FRAME_BASE_SIZE);
            break;
        }
        ret = _read_one_frame(pak, u, all+offset);
        if (ret < 0) {
            offset -= ret;
            LOG_WARN("read fail");
            continue;
        }
        cnt++;
        offset+=ret;
        LOG_INFO("%.*s read success\n", (int)sizeof(u->filename), u->filename);
        // LOG_DEBUG("offset = %d", offset);
        u->next = malloc(sizeof(struct p_module_unit));
        if (!u) break;
        u = u->next;
        u->next = NULL;
    }
    free(all);
    if (!cnt) {
        free(pak->unit);
        pak->unit = NULL;
    } else if (!u) {

    } else {
        ud = pak->unit;
        while (ud->next != u) ud = ud->next;
        free(ud->next);
        ud->next = NULL;
    }
    return cnt;
}

int package_frame_output_file(struct package_frame *pak, char *filename, char *dest_path) {
    if (!dest_path || dest_path[0]==0) {
        dest_path = ".";
    }
    struct p_module_unit *u = pak->unit;
    while (u) {
        if (strcmp(filename, u->filename) == 0) {
            char buff[sizeof(u->filename)];
            if (u->fileFolder[0] == 0) {
                sprintf(buff, "%s/%s", dest_path, filename);
            } else {
                snprintf(buff, sizeof(buff), "%s/%s", dest_path, u->fileFolder);
                if (mkdir_rd(buff, 1)) {
                    return -2;
                }
                snprintf(buff, sizeof(buff), "%s/%s/%s", dest_path, u->fileFolder, filename);
            }
            
            if (!u->data) return -2;
            FILE *fp = fopen(buff, "wb");
            if (!fp) {
                perror("fopen");
                return -1;
            }
            fwrite(u->data, 1, u->data_len, fp);
            fclose(fp);
            return 1;
        }
        u = u->next;
    }
    return 0;
}

int package_frame_output_all(struct package_frame *pak, char *dest_path) {
    if (!dest_path || dest_path[0]==0) {
        dest_path = ".";
    }
    if (!is_directory(dest_path)) {
        return -3;
    }
    int cnt=0;
    struct p_module_unit *u = pak->unit;
    while (u) {
        char buff[sizeof(u->filename)];
        if (u->fileFolder[0] == 0) {
            snprintf(buff, sizeof(buff), "%s/%s", dest_path, u->filename);
        } else {
            snprintf(buff, sizeof(buff), "%s/%s", dest_path, u->fileFolder);
            LOG_DEBUG("u->fileFolder = %s", buff);
            if (mkdir_rd(buff, 1)) {
                return -2;
            }
            snprintf(buff, sizeof(buff), "%s/%s/%s", dest_path, u->fileFolder, u->filename);
        }
        LOG_DEBUG("buff = %s", buff);
        buff[sizeof(buff)-1] = 0;
        if (u->data) {
            FILE *fp = fopen(buff, "wb");
            if (!fp) {
                perror("fopen");
                return -1;
            }
            fwrite(u->data, 1, u->data_len, fp);
            fclose(fp);
            LOG_DEBUG("output file %s", buff);
            cnt++;
        }
        u = u->next;
    }
    return cnt;
}

void merge_file(char *file1, char *file2, char *outfile) {
    char buff[256]={0};
    #if defined(_WIN32)
    if (strcmp(file2, outfile)==0) sprintf(buff, "copy /b %s+%s %s", file2, file1, outfile);
    else sprintf(buff, "copy /b %s+%s %s", file1, file2, outfile);
    #elif defined(__linux__)
    if (strcmp(file1, outfile)==0) sprintf(buff, "cat %s >> %s", file2, outfile);
    else if (strcmp(file2, outfile)==0)  sprintf(buff, "cat %s >> %s", file1, outfile);
    else sprintf(buff, "cat %s %s > %s", file1, file2, outfile);
    #elif defined(__APPLE__)
    sprintf(buff, "echo 'only apples can do'");
    #else
    sprintf(buff, "echo 'error to merge files'");
    #endif
    system(buff);
}
